﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace ProLinq.Toolkit
{
	public static class QueryProviderExtensions
	{
		#region Fields

		private static MethodInfo method_createQueryOfT;
		private static MethodInfo method_executeOfT;

		#endregion

		/// <summary>
		/// Calls CreateQuery&lt;T&gt;() method with T resolved from expression type.
		/// </summary>
		/// <param name="provider"></param>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static IQueryable CreateQueryOfT(this IQueryProvider provider, Expression expression)
		{
			var elementType = expression.Type.GetEnumerableType();
			return provider.CreateQueryOfT(elementType, expression);
		}

		/// <summary>
		/// Calls CreateQuery&lt;T&gt;() method.
		/// </summary>
		/// <param name="provider"></param>
		/// <param name="type"></param>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static IQueryable CreateQueryOfT(this IQueryProvider provider, Type type, Expression expression)
		{
			try
			{
				if (method_createQueryOfT == null)
				{
					method_createQueryOfT = typeof(IQueryProvider).GetMethods().First(m => m.Name == "CreateQuery" && m.IsGenericMethod);
				}

				return (IQueryable)method_createQueryOfT.MakeGenericMethod(type).Invoke(provider, new object[] { expression });
			}
			catch (TargetInvocationException exc)
			{
				throw exc.InnerException;
			}
		}

		/// <summary>
		/// Calls Execute&lt;T&gt;() method with T resolved from expression type.
		/// </summary>
		/// <param name="provider"></param>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static Object ExecuteOfT(this IQueryProvider provider, Expression expression)
		{
			var resultType = expression.Type;
			return provider.ExecuteOfT(resultType, expression);
		}


		/// <summary>
		/// Calls Execute&lt;T&gt;() method.
		/// </summary>
		/// <param name="provider"></param>
		/// <param name="type"></param>
		/// <param name="expression"></param>
		/// <returns></returns>
		public static Object ExecuteOfT(this IQueryProvider provider, Type type, Expression expression)
		{
			try
			{
				if (method_executeOfT == null)
				{
					method_executeOfT = typeof(IQueryProvider).GetMethods().First(m => m.Name == "Execute" && m.IsGenericMethod);
				}

				return method_executeOfT.MakeGenericMethod(type).Invoke(provider, new object[] { expression });
			}
			catch (TargetInvocationException exc)
			{
				throw exc.InnerException;
			}
		}
	}
}
